package root.loan;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import root.historic.event.EmprestimoEvent;
import root.item.GerenciadorItem;
import root.item.Item;
import root.message.MessageType;
import root.user.User;
import strategy.GerenciadorEmprestimoIF;
import strategy.GerenciadorItemIF;
import exceptions.AbsentAtributException;
import exceptions.BorrowDoesNotExist;
import exceptions.HasAlreadyDoneException;
import exceptions.InvalidAtributException;
import exceptions.InvalidBorrowId;
import exceptions.InvalidDurationException;
import exceptions.InvalidMessage;
import exceptions.InvalidReceiver;
import exceptions.InvalidSender;
import exceptions.InvalidSubject;
import exceptions.IsNotOnwerException;
import exceptions.ItemDoesNotExist;
import exceptions.ItemHasAlreadyBorrowedException;
import exceptions.ItemRequestAlreadyExistsException;
import exceptions.LoanHasAlreadyBeenApprovedException;
import exceptions.PermissionDeniedException;
import exceptions.WithoutPermissionRequestLoanException;

public class GerenciadorEmprestimo implements GerenciadorEmprestimoIF{

	private Collection<Borrow > emprestimosBeneficiado;
	private Collection<Borrow > emprestimosEmprestador;
	private Map<String,Borrow> loanRequest;
	private static int nextId = 0;
	protected GerenciadorItemIF gerenciadorItem ;
	private User user;
	private List<EmprestimoListener> listeners;
	
	public GerenciadorEmprestimo(User user, GerenciadorItemIF gerenciadorItem){
		emprestimosBeneficiado = new HashSet<Borrow>();
		emprestimosEmprestador = new HashSet<Borrow>();
		loanRequest = new HashMap<String, Borrow>();
		this.gerenciadorItem = gerenciadorItem;
		this.user = user;
		listeners = new ArrayList<EmprestimoListener>();
	}
	
	
	public Borrow getBorrow(String id) throws BorrowDoesNotExist, InvalidBorrowId {
		if(id == null || id.equals("")){
			throw new InvalidBorrowId("Identificador do empréstimo é inválido");
		}
		if (!hasBorrow(id)) {
			throw new BorrowDoesNotExist("Empréstimo inexistente");
		}
		return loanRequest.get(id);
	}
	
	public Borrow getRequestBorrow(String id) throws BorrowDoesNotExist, InvalidBorrowId {
		if(id == null || id.equals("")){
			throw new InvalidBorrowId("Identificador da requisição de empréstimo é inválido");
		}
		if (!hasBorrow(id)) {
			throw new BorrowDoesNotExist("Requisição de empréstimo inexistente");
		}
		return loanRequest.get(id);
	}
	
	
	/**
	 * Verifica se uma indentificacao de resquisicao de emprestimo ou um emprestimo existe
	 * @param id Identificacao de uma requisicao de emprestimo ou de um emprestimo
	 * @return {@code true} Se a identificacao existir, {@code false} caso contrario
	 */
	public boolean hasBorrow(String id) {
		if (id == null) {
			return false;
		}
		return loanRequest.containsKey(id);
	}
	
	public String getNewAvaibleID() {
		String returnValue = String.valueOf(nextId++);
		return returnValue;
	}
	
	/**
	 * O usuario {@code user} requisita o emprestimo do item de id 'itemId' por 'days' dias.
	 * @param user
	 * @param itemId
	 * @param days
	 * @return
	 * @throws ItemDoesNotExist
	 * @throws ItemRequestAlreadyExistsException
	 * @throws WithoutPermissionRequestLoanException
	 * @throws InvalidDurationException
	 * @throws AbsentAtributException 
	 * @throws InvalidAtributException 
	 * @throws InvalidReceiver 
	 * @throws InvalidSubject 
	 * @throws InvalidMessage 
	 * @throws IsNotOnwerException 
	 */
	public String borrowItem(Item item, int days, GerenciadorEmprestimoIF gEmprestimoDonoItem) throws ItemDoesNotExist, ItemRequestAlreadyExistsException, WithoutPermissionRequestLoanException, InvalidDurationException, InvalidAtributException, AbsentAtributException, InvalidMessage, InvalidSubject, InvalidReceiver, IsNotOnwerException{
		if(days <= 0){
			throw new InvalidDurationException("Duracao inválida");
		}
		if( hasRequestBorrow(item) ){
			throw new ItemRequestAlreadyExistsException("Requisição já solicitada");
		}
		User owner = gerenciadorItem.getOwner(item);
		if(!user.isFriend(owner)){
			throw new WithoutPermissionRequestLoanException("O usuário não tem permissão para requisitar o empréstimo deste item");
		}
		requisitouEmprestimo(new EmprestimoEvent(owner, user, item));
		String id = getNewAvaibleID();
		Borrow borrow = new Borrow(id,user, item, days);
		loanRequest.put(id, borrow);
		emprestimosBeneficiado.add(borrow);
		gEmprestimoDonoItem.addBorrowEmprestador(borrow);
		return id;
	}
	
	private void requisitouEmprestimo(EmprestimoEvent emprestimoEvent) {
		for(EmprestimoListener listener : listeners){
			listener.notifyRequisitarEmprestimo(emprestimoEvent);
		}
		
	}


	/**
	 * O usuario {@code user} indica devolucao do item emprestado ao dono e aguarda confirmacao
	 * @param user
	 * @param id Identificacao de emprestimo do item
	 * @throws PermissionDeniedException
	 * @throws HasAlreadyDoneException
	 * @throws BorrowDoesNotExist
	 * @throws InvalidBorrowId
	 */
	public void returnItem(String borrowId) throws PermissionDeniedException, HasAlreadyDoneException, BorrowDoesNotExist, InvalidBorrowId{
		Borrow borrow = getBorrow(borrowId);
		if(!borrow.getInterested().equals(user)){
			throw new PermissionDeniedException("O item só pode ser devolvido pelo usuário beneficiado");
		}

		borrow.returnItem();
	
		
	}
	
	/**
	 * O usuario {@code user}  requisita a devolucao do seu item de identificacao {@code id} 
	 * @param user
	 * @param id
	 * @throws BorrowDoesNotExist
	 * @throws InvalidBorrowId
	 * @throws HasAlreadyDoneException
	 * @throws PermissionDeniedException
	 * @throws ItemDoesNotExist
	 * @throws InvalidAtributException
	 * @throws AbsentAtributException
	 * @throws InvalidMessage
	 * @throws InvalidSubject
	 * @throws InvalidReceiver
	 */
	public void requestDevolution(String borrowId) throws BorrowDoesNotExist, InvalidBorrowId, HasAlreadyDoneException, PermissionDeniedException, ItemDoesNotExist, InvalidAtributException, AbsentAtributException, InvalidMessage, InvalidSubject, InvalidReceiver{
		Borrow borrow = getBorrow(borrowId);
		if(!gerenciadorItem.getOwner(borrow.getItem()).equals(user)){
			throw new HasAlreadyDoneException("O usuário não tem permissão para requisitar a devolução deste item");
		}
		if( CurrentDate.getInstance().getActualDate().get(Calendar.DATE) < borrow.expecteDayOfDevolution().get(Calendar.DATE)){
			borrow.cancel();
		}else{
			borrow.requestDevolution();
		}
		requisicaoSolicitada(new EmprestimoEvent( user, borrow.getInterested(),borrow.getItem()));
			
	}
	
	
	private void requisicaoSolicitada(EmprestimoEvent emprestimoEvent) {
		for(EmprestimoListener listener : listeners){
			listener.notifyRequisicaoDeDevolucao(emprestimoEvent);
		}
	}


	/**
	 * O usuario {@code user} confirma a devolucao do item emprestado .
	 * @param user
	 * @param id Identificacao de emprestimo do item
	 * @throws PermissionDeniedException
	 * @throws HasAlreadyDoneException
	 * @throws ItemDoesNotExist
	 * @throws InvalidBorrowId
	 * @throws BorrowDoesNotExist
	 */
	public void completeLoan(String borrowId) throws PermissionDeniedException, HasAlreadyDoneException, ItemDoesNotExist, InvalidBorrowId, BorrowDoesNotExist{
		Borrow borrow = getBorrow(borrowId);
		if(!gerenciadorItem.getOwner(borrow.getItem()).equals(user)){
			throw new PermissionDeniedException("O término do empréstimo só pode ser confirmado pelo dono do item");
		}
		borrow.complete();
		gerenciadorItem.devolution(borrow.getItem());
		user.incremetBorrows();
		for(EmprestimoListener listener : listeners){
			listener.notifyTerminoDeEmprestimo(new EmprestimoEvent(user,gerenciadorItem.getOwner(borrow.getItem()), borrow.getItem()));
		}
		
	}
	
	private boolean hasRequestBorrow(Item item) {
		if(item == null){
			return false;
		}
		for(Borrow borrow : emprestimosBeneficiado){
			if(borrow.getInterested().equals(user) && item.equals(borrow.getItem()) &&  !borrow.isComplete()){
				return true;
			}
		}
		return false;
	}
	
	
	/**
	 *  O dono do item aprova uma requisicao de emprestimo de idntificacao {@code id}
	 * @param user Dono do item
	 * @param id
	 * @return
	 * @throws IsNotOnwerException
	 * @throws BorrowDoesNotExist
	 * @throws ItemDoesNotExist
	 * @throws LoanHasAlreadyBeenApprovedException
	 * @throws ItemHasAlreadyBorrowedException
	 * @throws InvalidBorrowId
	 * @throws PermissionDeniedException 
	 */
	public String approveLoan(String borrowId) throws IsNotOnwerException, BorrowDoesNotExist, ItemDoesNotExist, LoanHasAlreadyBeenApprovedException, ItemHasAlreadyBorrowedException, InvalidBorrowId, PermissionDeniedException{
		Borrow borrow = getRequestBorrow(borrowId);
		if(!gerenciadorItem.getOwner(borrow.getItem()).equals(user)){
			throw new IsNotOnwerException("O empréstimo só pode ser aprovado pelo dono do item");
		}
		borrow.aproveBorrow(CurrentDate.getInstance().getActualDate());
		gerenciadorItem.lend(borrow.getItem(), borrow.getInterested(), borrow.getDays());
		addBorrowEmprestador(borrow);
		for (EmprestimoListener listener : listeners) {
			
			listener.notifyEmprestimoAprovado(new EmprestimoEvent(user, borrow.getInterested(),borrow.getItem()));
		}
		return borrowId;
	}

	private Collection<Borrow> getEmprestimosEmprestador(){
		Collection<Borrow> resp = new ArrayList<Borrow>();
		for(Borrow borrow : emprestimosEmprestador){
			try{
				if(gerenciadorItem.getOwner(borrow.getItem()).equals(user) && !borrow.isRequest()){
					resp.add(borrow);
				}
			} catch (Exception e){
				
			}
		}
		return resp;
	}
	
	private Collection<Borrow> getEmprestimosBeneficiado(){
		Collection<Borrow> resp = new ArrayList<Borrow>();
		for(Borrow borrow : emprestimosBeneficiado){
			if(borrow.getInterested().equals(user) && !borrow.isRequest()){
				resp.add(borrow);
			}
		}
		return resp;
	}
	
	
	private String getDescricaoEmprestimos(Collection<Borrow> emprestimos) throws InvalidAtributException, AbsentAtributException, ItemDoesNotExist{
		String resp = "";
		int cont = 1;
		for(Borrow borrow : emprestimos){
			
				User owner = gerenciadorItem.getOwner(borrow.getItem());
				String nomeItem = gerenciadorItem.getAtributoItem(borrow.getItem(), "nome");
				resp =resp.concat( owner.getUsername() + "-" + borrow.getInterested().getUsername() + ":" + nomeItem+":"+borrow.getDescricao());
				if(cont != emprestimos.size()){
					resp = resp.concat("; ");
				}
				cont++;
			
				
			
		}
		if(emprestimos.size() == 0){
			resp = "Não há empréstimos deste tipo";
		}
		return resp;
	}
	
	/**
	 * Retorna uma String contendo todos os emprestimos do usuario {@code user} sendo {@code "beneficiado"}, '{@code "emprestador"} ou ambos {@code "todos"}.
	 *  
	 * @param user
	 * @param atributo
	 * @return
	 * @throws InvalidAtributException
	 * @throws AbsentAtributException
	 * @throws ItemDoesNotExist
	 */
	public String getEmprestimos(String atributo) throws InvalidAtributException, AbsentAtributException, ItemDoesNotExist{
		String resp="";
		if(atributo==null||atributo.equals("")){
			throw new InvalidAtributException("Tipo inválido");
		}
		else if(atributo.equals("emprestador")){
			resp = getDescricaoEmprestimos(getEmprestimosEmprestador());
		}
		else if(atributo.equals("beneficiado")){
			resp = getDescricaoEmprestimos(getEmprestimosBeneficiado());
		}
		else if(atributo.equals("todos")){
			Collection<Borrow> o = getEmprestimosEmprestador();
			o.addAll(getEmprestimosBeneficiado());
			resp = getDescricaoEmprestimos(o);
		}
		else{
			throw new AbsentAtributException("Tipo inexistente");
		}
		return resp;
		
	}
	
	/**
	 * Remove todas as requisicoes de emprestimo entre os usuarios {@code user1} e {@code user2}
	 * @param user1
	 * @param user2
	 */
	public void removesAllRequestsBetween(User user2){
		Iterator<Borrow> it = emprestimosBeneficiado.iterator();
		while(it.hasNext()){
			Borrow borrow = it.next();
			if(gerenciadorItem.getOwner(borrow.getItem()).equals(user2) && borrow.isRequest()){
				it.remove();
				loanRequest.remove(borrow.getId());
			}
		}
		it = emprestimosEmprestador.iterator();
		while(it.hasNext()){
			Borrow borrow = it.next();
			if(borrow.getInterested().equals(user2) && borrow.isRequest()){
				it.remove();
				loanRequest.remove(borrow.getId());
			}
		}
	}
	
	/**
	 * 
	 * @param user
	 * @param id
	 * @throws Exception Se o Usuario {@code user} nao participa nem como beneficiario nem como emprestador do emprestimo
	 */
	public void belongsLoan(String borrowId) throws Exception{
		if (!(getRequestBorrow(borrowId).getInterested().equals(user) || gerenciadorItem.getOwner(getRequestBorrow(borrowId).getItem()).equals(user))){
			throw new Exception("O usuário não participa deste empréstimo");
		}
	}
	
	/**
	 * Remove todas as requisicoes de emprestimo para o item de identificacao {@code id}
	 * @param idItem
	 */
	public void removesRequest(Item item){
		Iterator<Borrow> it = emprestimosBeneficiado.iterator();
		while(it.hasNext()){
			Borrow borrow = it.next();
			if(borrow.getItem().equals(item) && borrow.isRequest()){
				it.remove();
				loanRequest.remove(borrow.getId());
			}
		}
		it = emprestimosEmprestador.iterator();
		while(it.hasNext()){
			Borrow borrow = it.next();
			if(borrow.getItem().equals(item) && borrow.isRequest()){
				it.remove();
				loanRequest.remove(borrow.getId());
			}
		}
	}
	
	public void addBorrowEmprestador(Borrow borrow) throws IsNotOnwerException{
		if(!gerenciadorItem.getOwner(borrow.getItem()).equals(user)){
			throw new IsNotOnwerException("O empréstimo só pode ser aprovado pelo dono do item");
		}
		if(!loanRequest.containsKey(borrow.getId())) loanRequest.put(borrow.getId(), borrow);
		emprestimosEmprestador.add(borrow);
	}

	public void addListener(EmprestimoListener listener) {
		listeners.add(listener);
		
	}
	
}
